﻿using System;
using System.Collections.Generic;
using System.Linq;
using System.Text;
using System.Collections;
using System.Reflection;

namespace EasyCLRBase.AssemblyExplorer
{
    /// <summary>
    /// mimic of the MEF class CompositionContainer to use compatible structur for CLR creation.
    /// </summary>
    public class CompositionContainer : IDisposable
    {
        internal class TypeExplorer
        {
            public FieldInfo ContainerField;
            public Object ContractType;
            public List<Object> DiscoveredTypes;
            public Object ContainerObject;

            public TypeExplorer()
            {
                this.DiscoveredTypes = new List<Object>();
            }

            internal void SetValue()
            {
                Type listType = null, fullNameType = null;
                Object list = null;
                ///get the full name Type for the Activator.
                fullNameType = Type.GetType(String.Concat(this.ContainerField.FieldType.Namespace, ".", this.ContainerField.FieldType.Name));
                ///If the new Type is List, so create it into listType.
                if (this.ContainerField.FieldType.Name.Contains("List") == true)
                    listType = fullNameType.MakeGenericType((Type)this.ContractType);
                if (listType == null)
                    list = Activator.CreateInstance(fullNameType, new Object[] { this.DiscoveredTypes.Count });
                else
                    list = Activator.CreateInstance(listType);
                ///Copy data into the given list or array
                if (fullNameType.Name.Contains("List") == true)
                    this.DiscoveredTypes.ForEach(a => ((IList)list).Add(a));
                else
                    this.DiscoveredTypes.CopyTo((Object[])list);

                this.ContainerField.SetValue(this.ContainerObject, list);
            }
        }

        public List<System.Reflection.Assembly> Assemblies;

        /// <summary>
        /// Directory Catalag for scanned data.
        /// </summary>
        private DirectoryCatalog Catalog;
        /// <summary>
        /// Initialize mimic CompositionContainer of the MEF
        /// </summary>
        /// <param name="catalog">Using Directory Catalog</param>
        public CompositionContainer(DirectoryCatalog catalog)
        {
            this.Catalog = catalog;
            this.Assemblies = new List<Assembly>();
        }
        /// <summary>
        /// mimic of the MEF ComposeParts. Write the parts into given objects.
        /// </summary>
        /// <param name="attributedParts">object that are used to writes the parts down.</param>
        public void ComposeParts(params Object[] attributedParts)
        {
            try
            {
                ///read all import Types.
                var exportTypes = new List<TypeExplorer>();
                foreach (var obj in attributedParts)
                {
                    foreach (var field in obj.GetType().GetFields(BindingFlags.NonPublic | BindingFlags.Public | BindingFlags.Instance))
                    {
                        var imports = (ImportManyAttribute[])field.GetCustomAttributes(typeof(ImportManyAttribute), true);
                        if (imports != null && imports.Length > 0)
                        {
                            exportTypes.Add(new TypeExplorer { ContainerField = field, ContractType = imports.First().ContractType, ContainerObject = obj });
                        }
                    }
                }
                ///walk through all assemblies from catalog and get all export types.
                foreach (var assembly in this.Catalog.LoadAssemblies())
                {
                    var attr = assembly.GetCustomAttributes(typeof(ClrImportableAttribute), false);
                    if (attr.Length == 0)
                        continue;
                    this.Assemblies.Add(assembly);
                    foreach (var type in assembly.GetTypes())
                    {
                        ///if type is failover, do not read it attribute.
                        var Failover = type.GetInterface(typeof(IClrFailoverBase).FullName);
                        ExportAttribute[] attrs = new ExportAttribute[0];
                        if (Failover == null)
                        {
                            attrs = (ExportAttribute[])type.GetCustomAttributes(typeof(ExportAttribute), false);
                            if (attrs.Length == 0)
                                continue;
                        }
                        foreach (var value in exportTypes)
                        {
                            var exportAttr = attrs.Where(a => Type.Equals(a.ContractType, value.ContractType) == true);
                            var cType = type.GetInterface(((Type)value.ContractType).FullName);

                            ///Check if it has the conveniet interface and export attribute.
                            if (cType != null && type.IsInterface == false && type.IsAbstract == false && (exportAttr.Count() > 0 || Failover != null))
                            {
                                var inst = Activator.CreateInstance(type);
                                value.DiscoveredTypes.Add(inst);
                            }
                        }
                    }
                }
                ///set all import fields with specific values.
                exportTypes.ForEach(a => a.SetValue());
            }
            catch (Exception e)
            {
                e.Data["LastFile"] = this.Catalog.LastLoadingFile;
                throw;
            }
        }

        #region Disposed
        /// <summary>
        /// Finalizer
        /// </summary>
        ~CompositionContainer()
        {
            this.Dispose(false);
        }
        /// <summary>
        /// Managed/Unmanaged Dispose
        /// </summary>
        /// <param name="disposing">true => managed dispose</param>
        private void Dispose(Boolean disposing)
        {
            if (disposing == true)
            {
                this.Catalog.Dispose();
                this.Assemblies.Clear();
            }
            this.Catalog = null;
            this.Assemblies = null;
        }
        /// <summary>
        /// Call Dispose to clean up this object.
        /// </summary>
        public void Dispose()
        {
            Dispose(true);
            // Use SupressFinalize in case a subclass 
            // of this type implements a finalizer.
            GC.SuppressFinalize(this);
        }
        #endregion
    }
}
